/*
 * File:   engine_impl.cpp
 * Author: dolphinigle
 *
 * Created on December 1, 2011, 5:27 PM
 */

#include <algorithm>
#include <cassert>
#include <vector>

#include <sys/types.h>  // For stat().
#include <sys/stat.h>   // For stat().

#include "engine_impl.h"
#include "file_system.h"
#include "ipc_controller_mock.h"
#include "ipc_controller.h"
#include "ipc_receiver.h"
#include "path.h"
#include "util.h"

using std::make_pair;
using std::vector;

EngineImpl::EngineImpl() : current_directory_("/"), next_msq_id_(101) {
}

pair<string, IPCControllerInterface*> EngineImpl::FindAndStrip(
    const Path& path) {
  for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
    if (path.path().substr(0, it->first.size()) == it->first) {
      // Found
      return make_pair(path.path().substr(it->first.size() - 1,
                       path.path().size()), it->second);
    }
  }
  assert(false);
}

Path EngineImpl::MakeAbsolute(const Path& path) {
  assert(path.path() != "");
  if (path.path()[0] == '/') {
    return path;
  }
  if (path.is_virtual()) {
    return Path(current_directory_ + path.path(), /* is virtual = */ true);
  } else {
    char resolved_path[256];
    realpath(path.path().c_str(), resolved_path);
    return Path(resolved_path, /* is virtual = */ false);
  }
}

bool EngineImpl::IsExist(const Path& path) {
  if (path.is_virtual()) {
    if (path.path() == "/") return true;
    for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
      if (path.path().size() < it->first.size()) continue;
      if (path.path().substr(0, it->first.size()) == it->first) {
        pair<string, IPCControllerInterface*> shortened = FindAndStrip(path);
        return shortened.second->IsExist(shortened.first);
      }
    }
    return 0;
  } else {
    struct stat st;
    if (stat(path.path().c_str(), &st) == 0) {
      return 1;
    } else {
      return 0;
    }
  }
  assert(false);
}

bool EngineImpl::IsContainingFolderExists(const Path& path) {
  if (path.path() == "/") return true;
  string subpath = "";
  int break_location = 0;
  for (int i = path.path().size() - 2; i >= 0; --i) {
    if (path.path()[i] == '/') {
      break_location = i;
      break;
    }
  }
  if (path.path().substr(0, break_location + 1) == "/") return false;
  return IsExist(Path(path.path().substr(0, break_location + 1),
          path.is_virtual()));
}

string EngineImpl::Format(const Path& path, size_t size) {
  Path abs_path = MakeAbsolute(path);
  if (IsExist(abs_path)) {
    return abs_path.ToString() + " already exists!";
  }
  return FileSystem::Format(abs_path.path(), size);
}

string EngineImpl::MountFSTest(const Path& filesystem_location,
                               const Path& mount_point) {
  return DoMountFS(filesystem_location, mount_point, /*test = */ true);
}

string EngineImpl::MountFS(const Path& filesystem_location,
                           const Path& mount_point) {
  return DoMountFS(filesystem_location, mount_point, /*test = */ false);
}

string EngineImpl::DoMountFS(const Path& filesystem_location,
        const Path& mount_point, bool is_test) {
  assert(!filesystem_location.is_virtual());
  assert(mount_point.is_virtual());
  Path abs_fs_location = MakeAbsolute(filesystem_location);
  Path abs_mount_point = MakeAbsolute(mount_point);

  if (!IsExist(abs_fs_location)) {
    return "File " + abs_fs_location.ToString() + " not found";
  }
  if (IsExist(abs_mount_point)) {
    return "Directory " + abs_mount_point.ToString() + " already exists!";
  }

  int slash_count = 0;
  for (int i = 0; i < abs_mount_point.path().size(); ++i) {
    if (abs_mount_point.path()[i] == '/') ++slash_count;
  }

  if (slash_count != 2) {
    return "Mountfs can only be called in directory directly inheriting from /."
            " For example, /abc/ may be legal but /abc/def/ is not";
  }

  int to_receiver_msq_id = next_msq_id_++;
  int from_receiver_msq_id = next_msq_id_++;

  IPCControllerInterface* interface;
  if (is_test) {
    interface = new IPCControllerMock();
  } else {
    interface = new IPCController(to_receiver_msq_id, from_receiver_msq_id);
  }

  fs_.push_back(make_pair(abs_mount_point.path(), interface));

  // Executes another process that runs the file system.
  if (!is_test && fork() == 0) {
    IPCReceiver receiver(
        abs_fs_location.path(), to_receiver_msq_id, from_receiver_msq_id);
    // It should never hits this because it should terminate within the message.
    assert(false);
  }
  return abs_fs_location.ToString() + " successfully mounted on " +
         abs_mount_point.ToString();
}

string EngineImpl::Unmount(const Path& mount_point) {
  Path abs_mount_point = MakeAbsolute(mount_point);
  for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
    if (it->first == abs_mount_point.path()) {
      // Found
      string hasil = it->second->Close() + ". Current directory is reset";
      delete it->second;
      fs_.erase(it);
      current_directory_ = "/";
      return hasil;
    }
  }
  return "No device is mounted on " + abs_mount_point.ToString();
}

string EngineImpl::Pwd() {
  return current_directory_;
}

string EngineImpl::Ls() {
  return LsDirectory(current_directory_);
}

string EngineImpl::LsDirectory(const Path& directory) {
  Path abs_directory = MakeAbsolute(directory);
  if (!IsExist(abs_directory)) {
    return "Directory " + abs_directory.ToString() + " does not exists!";
  }
  if (abs_directory.path() == "/") {
    return LsRoot();
  }
  pair<string, IPCControllerInterface*> location = FindAndStrip(abs_directory);
  return location.second->Ls(location.first);
}

string EngineImpl::LsRoot() {
  string return_value = "";
  for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
    if (return_value != "") return_value += "\n";
    return_value += it->first;
  }
  return return_value;
}

string EngineImpl::Cd(const Path& directory) {
  Path abs_directory = MakeAbsolute(directory);
  if (!IsExist(abs_directory)) {
    return "Directory " + abs_directory.ToString() + " does not exists!";
  }
  current_directory_ = abs_directory.path();
  return "Current directory changed to " + current_directory_;
}

string EngineImpl::Mkdir(const Path& path) {
  Path abs_directory = MakeAbsolute(path);
  if (IsExist(abs_directory)) {
    return "Directory " + abs_directory.ToString() + " already exists!";
  }
  if (!IsContainingFolderExists(abs_directory)) {
    return "The directory containing " + abs_directory.ToString() +
           " does not exists!";
  }
  pair<string, IPCControllerInterface*> location = FindAndStrip(abs_directory);
  return location.second->Mkdir(location.first);
}

string EngineImpl::Rm(const Path& path) {
  Path abs_file = MakeAbsolute(path);
  if (!IsExist(abs_file)) {
    return abs_file.ToString() + " does not exists!";
  }
  if (abs_file.path() == "/") {
    return "You cannot remove root";
  }
  pair<string, IPCControllerInterface*> location = FindAndStrip(abs_file);
  return location.second->Rm(location.first);
}

string EngineImpl::Cp(const Path& origin, const Path& destination) {
  Path abs_origin = MakeAbsolute(origin);
  Path abs_dest = MakeAbsolute(destination);

  if (!IsExist(abs_origin)) {
    return abs_origin.ToString() + " does not exists!";
  }
  if (IsExist(abs_dest)) {
    return abs_dest.ToString() + " already exist!";
  }

  assert(abs_origin.is_virtual() || abs_dest.is_virtual());

  if (IsFileName(abs_origin.path()) != IsFileName(abs_dest.path())) {
    return abs_origin.ToString() + " and " + abs_dest.ToString() +
           " must be either both files or both folders";
  }

  if ((abs_dest.path() == "/" && abs_dest.is_virtual()) ||
      (abs_origin.path() == "/" && abs_dest.is_virtual())) {
    return "You cannot copy to or from root";
  }

  if (!IsContainingFolderExists(abs_dest)) {
    return "The folder containing " + abs_dest.ToString() + " must exists!";
  }

  pair<string, IPCControllerInterface*> origin_fs, dest_fs;
  if (abs_origin.is_virtual()) origin_fs = FindAndStrip(abs_origin);
  if (abs_dest.is_virtual()) dest_fs = FindAndStrip(abs_dest);
  if (!abs_origin.is_virtual()) {
    return dest_fs.second->Cp2(abs_origin.path(), dest_fs.first);
  }
  if (!abs_dest.is_virtual()) {
    return origin_fs.second->Cp1(origin_fs.first, abs_dest.path());
  }

  string temporary_file = "VERY_UGLY_TEMPORARY_FILE_NAME.TMP";
  if (IsDirectoryName(abs_origin.path())) {
    temporary_file = "VERY_UGLY_TEMPORARY_FOLDER_NAME";
  }
  Path temporary_file_path = MakeAbsolute(
          Path(temporary_file, /* virtual = */ false));

  string result = "";
  result += origin_fs.second->Cp1(origin_fs.first, temporary_file_path.path());
  result += "\n";
  result += dest_fs.second->Cp2(temporary_file_path.path(), dest_fs.first);

  // delete the temporary file.
  if (IsDirectoryName(abs_origin.path())) {
    assert(temporary_file_path.path().size() > 20);
    string command = "rm -r " + temporary_file_path.path();
    system(command.c_str());
  } else {
    remove(temporary_file_path.path().c_str());
  }

  return result;
}

string EngineImpl::Mv(const Path& origin, const Path& destination) {
  string hasil = Cp(origin, destination);

  Path abs_origin = MakeAbsolute(origin);
  if (abs_origin.is_virtual()) {
    hasil += "\n";
    hasil += Rm(abs_origin);
  } else {
    hasil += "\n";

    if (IsFileName(abs_origin.path())) {
      hasil += "For safety measures, we do not remove " + abs_origin.path() +
               " from the disk. Please do this yourself";
    } else {
      hasil += "For safety measures, we do not removes the folder. Please "
               "execute rm -r yourself at your own risk";
    }
  }

  return hasil;
}

string EngineImpl::Cat(const Path& filepath) {
  Path abs_file = MakeAbsolute(filepath);
  if (!IsExist(abs_file)) {
    return "File " + abs_file.ToString() + " does not exists!";
  }
  pair<string, IPCControllerInterface*> location = FindAndStrip(abs_file);
  return location.second->Cat(location.first);
}

string EngineImpl::Status() {
  string result = "";
  for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
    if (result != "") result += "\n";
    result += "mount_point='" + it->first + "' ";
    result += it->second->Status();
  }
  return result;
}

string EngineImpl::Exit() {
  vector<string> to_be_unmounted;
  for (FsIt it = fs_.begin(); it != fs_.end(); ++it) {
    to_be_unmounted.push_back(it->first);
  }

  string result = "";
  for (size_t i = 0; i < to_be_unmounted.size(); ++i) {
    if (result != "") result += "\n";
    result += Unmount(Path(to_be_unmounted[i], /* virtual = */ true));
  }

  if (result != "") result += "\n";
  result += "All file systems has been unmounted";
  return result;
}
